/* Copyright (C) 2005, 2006 by Daniel Gheorghian
 All rights reserved

 Use and distribute freely, except: don't remove my name from the source or documentation
 (don't take credit for my work), mark your changes (don't get me blamed for your possible bugs),
 don't alter or remove this notice.

 No warrantee of any kind, express or implied, is included with this software; use at your
 own risk, responsibility for damages (if any) to anyone resulting from the use of this
 software rests entirely with the user.

 Send bug reports, bug fixes, enhancements, requests, flames, etc., and I'll try to keep
 a version up to date.  I can be reached at: daniel.gheorghian@gmail.com
*/

package com.ds.components.grid;

import com.ds.components.interactors.Interactor;
import com.ds.components.interactors.InteractorEvent;
import com.ds.components.layout.DefaultDisplayLayout;
import com.ds.components.layout.DisplayLayoutInterface;
import com.ds.renderer.CellRenderer;
import com.ds.renderer.GenericRenderer;
import com.ds.renderer.HeaderRenderer;
import com.ds.theme.ColorTheme;

import javax.swing.*;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import java.awt.*;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.*;
import java.util.List;

public abstract class AbstractDisplayGrid extends JComponent implements ChangeListener, PropertyChangeListener
{
	protected Map<Class, CellRenderer> renderers = new Hashtable<Class, CellRenderer>();
	protected HeaderRenderer topHeaderRenderer = null;
	protected HeaderRenderer leftHeaderRenderer = null;
	protected GenericRenderer glassPaneRenderer = null;
	protected DisplayLayoutInterface layout = new DefaultDisplayLayout( 1, 1 );
	protected AbstractDisplayGridModel model;
	private Dimension cellSize = new Dimension();
	private boolean drawGrid;
	private boolean drawGridBorder;
	private Color gridColor;
	protected Hashtable<Integer, List<Interactor>> interactors = new Hashtable<Integer, List<Interactor>>();

	protected void init( AbstractDisplayGridModel model )
	{
		this.model = model;
		model.addChangeListener( this );
		layout.addPropertyChangeListener( this );
//		this.setMaximumSize( new Dimension( 10000, 30 ) );
//		this.setPreferredSize( new Dimension( 199, 30 ) );
//		this.setLayout( new FlowLayout( FlowLayout.LEFT, 50, 5 ) );

		updateUI();
	}

	public void setUI( AbstractDisplayGridUI ui )
	{
		super.setUI( ui );
	}

	public void updateUI()
	{
		setUI( ( AbstractDisplayGridUI ) UIManager.getUI( this ) );
		invalidate();
	}

	public String getUIClassID()
	{
		return "AbstractDisplayGrid";
	}

	public void stateChanged( ChangeEvent e )
	{
		repaint();
	}

	public void setModel( AbstractDisplayGridModel model )
	{
		AbstractDisplayGridModel old = this.model;

		this.model = model;

		firePropertyChange( "model", old, model );
	}

	public AbstractDisplayGridModel getModel()
	{
		return this.model;
	}

	public void setDrawGrid( boolean drawGrid )
	{
		this.drawGrid = drawGrid;
	}

	public boolean isDrawGrid()
	{
		return this.drawGrid;
	}

	protected Dimension getCellSize()
	{
		return this.cellSize;
	}

	public void addRenderer( Class classType, CellRenderer renderer )
	{
		this.renderers.put( classType, renderer );

		if( ( this.cellSize.width < renderer.getCellDimension().width )
		    || ( this.cellSize.height < renderer.getCellDimension().height ) )
		{
			this.cellSize = renderer.getCellDimension();
		}
	}

	public void removeRenderer( Class classType )
	{
		this.renderers.remove( classType );
		//todo solve cell size when removing a renderer
	}

	public CellRenderer getRenderer( Class classType )
	{
		return this.renderers.get( classType );
	}

	public void setDisplayLayout( DisplayLayoutInterface displayLayout )
	{
		this.layout = displayLayout;
		this.layout.addPropertyChangeListener( this );
	}

	public DisplayLayoutInterface getDisplayLayout()
	{
		return this.layout;
	}

	public Color getGridColor()
	{
		return gridColor;
	}

	public void setGridColor( Color gridColor )
	{
		this.gridColor = gridColor;
	}

	public Position addItem( Object item )
	{
		return addItem( item, -1, -1, 1, 1, null );
	}

	public Position addItem( Object item, ColorTheme colorTheme )
	{
		return addItem( item, -1, -1, 1, 1, colorTheme );
	}

	public Position addItem( Object item, int row, int column )
	{
		return addItem( item, row, column, 1, 1, null );
	}

	public Position addItem( Object item, int row, int column, int rowSpan, int colSpan )
	{
		return addItem( item, row, column, rowSpan, colSpan, null );
	}

	public Position addItem( Object item, int row, int column, int rowSpan, int colSpan, ColorTheme colorTheme )
	{
		if( column > model.getColumnsNo() )
		{
			column = -1;
			row = -1;
		}
		GridCell cell = new GridCell( row, column );
		cell.setCellSize( this.renderers.get( item.getClass() ).getCellDimension() );
		cell.setRowSpan( rowSpan );
		cell.setColSpan( colSpan );
		cell.setColorTheme( colorTheme );
		Position pos = layout.addCell( cell );

		model.addItem( pos, item );

		return pos;
	}

	public void removeItemByValue( Object item )
	{
		model.removeItemByValue( item );
	}

	public void removeItemByKey( Position key )
	{
		model.removeItemByKey( key );
		this.layout.removeCell( key );
	}

	public GridCell getCellFormatting( Position key )
	{
		return layout.getGridCell( key );
	}

	public Collection<Object> getItemList()
	{
		return model.getItemList();
	}

	public Object getItem( Position key )
	{
		return model.getItem( key );
	}

	public void setColumnsNo( int columnsNo )
	{
		model.setColumnsNo( columnsNo );
		layout.setColumnsNo( columnsNo );
		this.setPreferredSize( new Dimension( columnsNo * layout.getCellWidth() + layout.getLeftHeaderWidth() + 1, this.getPreferredSize().height ) );
	}

	public int getColumnsNo()
	{
		return model.getColumnsNo();
	}

	public boolean isDrawGridBorder()
	{
		return drawGridBorder;
	}

	public void setDrawGridBorder( boolean drawGridBorder )
	{
		this.drawGridBorder = drawGridBorder;
	}

	public void addCellInteractor( Interactor i )
	{
		List<Interactor> interactorList = this.interactors.get( i.getActivationMethodType() );

		if( interactorList == null )
		{
			interactorList = new ArrayList<Interactor>();
			this.interactors.put( i.getActivationMethodType(), interactorList );
		}

		interactorList.add( i );
	}

	public void removeCellInteractor( Interactor i )
	{
		List<Interactor> interactorList = this.interactors.get( i.getActivationMethodType() );

		if( interactorList != null )
		{
			interactorList.remove( i );
		}
	}

	public void removeAllItems()
	{
		model.removeAllItems();
		layout.removeAllItems();
	}

	public List<Interactor> getCellInteractors( int activationMethod )
	{
		return this.interactors.get( activationMethod );
	}

	public void fireInteractors( int activationMethodType, InteractorEvent e )
	{
		List<Interactor> interactorsList = this.interactors.get( activationMethodType );

		if( interactorsList != null )
		{
			for( Interactor i : interactorsList )
			{
				i.onActivation( e );
			}
		}
	}

	public HeaderRenderer getLeftHeaderRenderer()
	{
		return leftHeaderRenderer;
	}

	public void setLeftHeaderRenderer( HeaderRenderer leftHeaderRenderer )
	{
		if( leftHeaderRenderer == null )
		{
			layout.setLeftHeaderWidth( 0 );
		}
		else
		{
			layout.setLeftHeaderWidth( leftHeaderRenderer.getHeaderSize().width );
		}

		this.leftHeaderRenderer = leftHeaderRenderer;
	}

	public HeaderRenderer getTopHeaderRenderer()
	{
		return topHeaderRenderer;
	}

	public void setTopHeaderRenderer( HeaderRenderer topHeaderRenderer )
	{
		if( topHeaderRenderer == null )
		{
			layout.setTopHeaderHeight( 0 );
		}
		else
		{
			layout.setTopHeaderHeight( topHeaderRenderer.getHeaderSize().height );
		}

		this.topHeaderRenderer = topHeaderRenderer;
	}

	/**
	 * @return
	 * @see com.ds.components.layout.DisplayLayoutInterface#getCellHeight()
	 */
	public int getCellHeight()
	{
		return layout.getCellHeight();
	}

	/**
	 * @return
	 * @see com.ds.components.layout.DisplayLayoutInterface#getCellWidth()
	 */
	public int getCellWidth()
	{
		return layout.getCellWidth();
	}

	/**
	 * @return
	 * @see com.ds.components.layout.DisplayLayoutInterface#getHGap()
	 */
	public int getHGap()
	{
		return layout.getHGap();
	}

	/**
	 * @return
	 * @see com.ds.components.layout.DisplayLayoutInterface#getLeftHeaderWidth()
	 */
	public int getLeftHeaderWidth()
	{
		return layout.getLeftHeaderWidth();
	}

	/**
	 * @return
	 * @see com.ds.components.layout.DisplayLayoutInterface#getRowsNo()
	 */
	public int getRowsNo()
	{
		return layout.getRowsNo();
	}

	/**
	 * @return
	 * @see com.ds.components.layout.DisplayLayoutInterface#getTopHeaderHeight()
	 */
	public int getTopHeaderHeight()
	{
		return layout.getTopHeaderHeight();
	}

	/**
	 * @return
	 * @see com.ds.components.layout.DisplayLayoutInterface#getVGap()
	 */
	public int getVGap()
	{
		return layout.getVGap();
	}

	/**
	 * @param cellHeight
	 * @see com.ds.components.layout.DisplayLayoutInterface#setCellHeight(int)
	 */
	public void setCellHeight( int cellHeight )
	{
		layout.setCellHeight( cellHeight );
	}

	/**
	 * @param cellWidth
	 * @see com.ds.components.layout.DisplayLayoutInterface#setCellWidth(int)
	 */
	public void setCellWidth( int cellWidth )
	{
		layout.setCellWidth( cellWidth );
	}

	/**
	 * @param hGap
	 * @see com.ds.components.layout.DisplayLayoutInterface#setHGap(int)
	 */
	public void setHGap( int hGap )
	{
		layout.setHGap( hGap );
	}

	/**
	 * @param rightHeaderWidth
	 * @see com.ds.components.layout.DisplayLayoutInterface#setLeftHeaderWidth(int)
	 */
	public void setLeftHeaderWidth( int rightHeaderWidth )
	{
		layout.setLeftHeaderWidth( rightHeaderWidth );
	}

	/**
	 * @param rowsNo
	 * @see com.ds.components.layout.DisplayLayoutInterface#setRowsNo(int)
	 */
	public void setRowsNo( int rowsNo )
	{
		layout.setRowsNo( rowsNo );
		this.setPreferredSize( new Dimension( this.getPreferredSize().width, rowsNo * layout.getCellHeight() + layout.getTopHeaderHeight() + 1 ) );
	}

	/**
	 * @param topHeaderHeight
	 * @see com.ds.components.layout.DisplayLayoutInterface#setTopHeaderHeight(int)
	 */
	public void setTopHeaderHeight( int topHeaderHeight )
	{
		layout.setTopHeaderHeight( topHeaderHeight );
	}

	/**
	 * @param vGap
	 * @see com.ds.components.layout.DisplayLayoutInterface#setVGap(int)
	 */
	public void setVGap( int vGap )
	{
		layout.setVGap( vGap );
	}

	public void propertyChange( PropertyChangeEvent e )
	{
		if( e.getPropertyName().equals( "cell" ) )
		{
			Position oldPos = ( Position ) e.getOldValue();
			Position newPos = ( Position ) e.getNewValue();
			Object obj = model.getItem( oldPos );

			model.removeItemByKey( oldPos );
			model.addItem( newPos, obj );
		}
	}

	public GenericRenderer getGlassPaneRenderer()
	{
		return glassPaneRenderer;
	}

	public void setGlassPaneRenderer( GenericRenderer glassPaneRenderer )
	{
		this.glassPaneRenderer = glassPaneRenderer;
	}
}
